/********************************************************************+
# Copyright 2018-2019 Daniel 'grindhold' Brendle
#
# This file is part of phex.
#
# phex is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later
# version.
#
# phex is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with phex.
# If not, see http://www.gnu.org/licenses/.
*********************************************************************/

#include "common.h"

typedef struct _GLData GLData;
// GL related data here..

const float unit_matrix[] =
{
   1.0f, 0.0f, 0.0f, 0.0f,
   0.0f, 1.0f, 0.0f, 0.0f,
   0.0f, 0.0f, 1.0f, 0.0f,
   0.0f, 0.0f, 0.0f, 1.0f
};

const float vertices[] =
{
   -0.5f, -0.5f, 0.5f,
   -0.5f, -0.5f, -0.5f,
   0.5f, -0.5f, 0.5f,
   0.5f, -0.5f, 0.5f,
   -0.5f, -0.5f, -0.5f,
   0.5f, -0.5f, -0.5f
};

const float colors[] =
{
   1.0, 1.0, 1.0, 0.1f,
   1.0, 1.0, 1.0, 0.1f,
   1.0, 1.0, 1.0, 0.1f,
   1.0, 1.0, 1.0, 0.1f,
   1.0, 1.0, 1.0, 0.1f,
   1.0, 1.0, 1.0, 0.1f,
};

const float palette[] = {
    0.7f, 1.0f, 0.0f, 0.5f,
    0.0f, 0.2f, 0.9f, 0.5f,
    0.9f, 0.0f, 0.9f, 0.5f,
    0.9f, 0.2f, 0.0f, 0.5f
};

void
customLoadIdentity(float matrix[16])
{
   for (int i = 0; i < 16; i++)
        matrix[i] = unit_matrix[i];
}

void
customMutlMatrix(float matrix[16], const float matrix0[16], const float matrix1[16])
{
   int i, row, column;
   float temp[16];
   for (column = 0; column < 4; column++)
     {
        for (row = 0; row < 4; row++)
          {
             temp[column * 4 + row] = 0.0f;
             for (i = 0; i < 4; i++)
                  temp[column * 4 + row] += matrix0[i * 4 + row] * matrix1[column * 4 + i];
          }
     }
   for (i = 0; i < 16; i++)
        matrix[i] = temp[i];
}

void
customScale(float matrix[16], const float sx, const float sy, const float sz)
{
   matrix[0]  *= sx;
   matrix[1]  *= sx;
   matrix[2]  *= sx;
   matrix[3]  *= sx;

   matrix[4]  *= sy;
   matrix[5]  *= sy;
   matrix[6]  *= sy;
   matrix[7]  *= sy;

   matrix[8]  *= sz;
   matrix[9]  *= sz;
   matrix[10] *= sz;
   matrix[11] *= sz;
}

void
customRotate(float matrix[16], const float anglex, const float angley, const float anglez)
{
   const float pi = 3.141592f;
   float temp[16];
   float rz = 2.0f * pi * anglez / 360.0f;
   float rx = 2.0f * pi * anglex / 360.0f;
   float ry = 2.0f * pi * angley / 360.0f;
   float sy = sinf(ry);
   float cy = cosf(ry);
   float sx = sinf(rx);
   float cx = cosf(rx);
   float sz = sinf(rz);
   float cz = cosf(rz);

   customLoadIdentity(temp);

   temp[0] = cy * cz - sx * sy * sz;
   temp[1] = cz * sx * sy + cy * sz;
   temp[2] = -cx * sy;

   temp[4] = -cx * sz;
   temp[5] = cx * cz;
   temp[6] = sx;

   temp[8] = cz * sy + cy * sx * sz;
   temp[9] = -cy * cz * sx + sy * sz;
   temp[10] = cx * cy;

   customMutlMatrix(matrix, matrix, temp);
}

int
customFrustum(float result[16], const float left, const float right, const float bottom, const float top, const float near, const float far)
{
   if ((right - left) == 0.0f || (top - bottom) == 0.0f || (far - near) == 0.0f) return 0;

   result[0] = 2.0f / (right - left);
   result[1] = 0.0f;
   result[2] = 0.0f;
   result[3] = 0.0f;

   result[4] = 0.0f;
   result[5] = 2.0f / (top - bottom);
   result[6] = 0.0f;
   result[7] = 0.0f;

   result[8] = 0.0f;
   result[9] = 0.0f;
   result[10] = -2.0f / (far - near);
   result[11] = 0.0f;

   result[12] = -(right + left) / (right - left);
   result[13] = -(top + bottom) / (top - bottom);
   result[14] = -(far + near) / (far - near);
   result[15] = 1.0f;

   return 1;
}

int
init_shaders(GLData *gld)
{
   Evas_GL_API *gl = gld->glapi;

   GLbyte vertex_shader[] =
       "#ifdef GL_ES\n"
       "precision mediump float;\n"
       "#endif\n"
       "attribute vec4 a_position;\n"
       "attribute vec4 a_color;\n"
       "uniform mat4 u_mvp_mat;\n"
       "varying vec4 v_color;\n"
       "void main()\n"
       "{\n"
       "   gl_Position = u_mvp_mat*a_position;\n"
       "   v_color = a_color;\n"
       "}";
   GLbyte fragment_shader[] =
       "#ifdef GL_ES\n"
       "precision highp float;\n"
       "#endif\n"
       "varying vec4 v_color;\n"
       "void main()\n"
       "{\n"
       "   gl_FragColor = v_color;\n"
       "}";

   GLint compiled;
   const char *p = vertex_shader;

   //vertex shader
   gld->vtx_shader = gl->glCreateShader(GL_VERTEX_SHADER);
   gl->glShaderSource(gld->vtx_shader, 1, &p, NULL);
   gl->glCompileShader(gld->vtx_shader);
   gl->glGetShaderiv(gld->vtx_shader, GL_COMPILE_STATUS, &compiled);
   if (!compiled)
     {
        GLint info_len = 0;
        gl->glGetShaderiv(gld->vtx_shader, GL_INFO_LOG_LENGTH, &info_len);
        if (info_len > 1)
          {
             char* info_log = malloc(sizeof(char) * info_len);
             gl->glGetShaderInfoLog(gld->vtx_shader, info_len, NULL, info_log);
             printf("Error compiling shader:\n%s\n======\n%s\n======\n", info_log, p);
             free(info_log);
          }
        gl->glDeleteShader(gld->vtx_shader);
     }

   //fragment shader
   p = fragment_shader;
   gld->fgmt_shader = gl->glCreateShader(GL_FRAGMENT_SHADER);
   gl->glShaderSource(gld->fgmt_shader, 1, &p, NULL);
   gl->glCompileShader(gld->fgmt_shader);
   gl->glGetShaderiv(gld->fgmt_shader, GL_COMPILE_STATUS, &compiled);
   if (!compiled)
     {
        GLint info_len = 0;
        gl->glGetShaderiv(gld->fgmt_shader, GL_INFO_LOG_LENGTH, &info_len);
        if (info_len > 1)
          {
             char* info_log = malloc(sizeof(char) * info_len);
             gl->glGetShaderInfoLog(gld->fgmt_shader, info_len, NULL, info_log);
             printf("Error compiling shader:\n%s\n======\n%s\n======\n", info_log, p);
             free(info_log);
          }
        gl->glDeleteShader(gld->fgmt_shader);
     }

   gld->program = gl->glCreateProgram();

   GLint linked;
   // Load the vertex/fragment shaders
   gl->glAttachShader(gld->program, gld->vtx_shader);
   gl->glAttachShader(gld->program, gld->fgmt_shader);
   gl->glDeleteShader(gld->vtx_shader);
   gl->glDeleteShader(gld->fgmt_shader);

   gl->glBindAttribLocation(gld->program, 0, "a_position");
   gl->glLinkProgram(gld->program);
   gl->glGetProgramiv(gld->program, GL_LINK_STATUS, &linked);
   if (!linked)
     {
        GLint info_len = 0;
        gl->glGetProgramiv(gld->program, GL_INFO_LOG_LENGTH, &info_len);
        if (info_len > 1)
          {
             char* info_log = malloc(sizeof(char) * info_len);
             gl->glGetProgramInfoLog(gld->program, info_len, NULL, info_log);
             printf("Error linking program:\n%s\n", info_log);
             free(info_log);
          }
        gl->glDeleteProgram(gld->program);
        return 0;
     }
   gld->mvpLoc     = gl->glGetUniformLocation(gld->program, "u_mvp_mat");
   gld->positionLoc = gl->glGetAttribLocation(gld->program, "a_position");
   gld->colorLoc   = gl->glGetAttribLocation(gld->program, "a_color");
   return 1;
}

// TODO: get rid of or fix heisenbug
//       elm_slider_value_get results in 0.0 no matter what
//       when evaluating the term (double)elm_slider_value_get(pd->coods[i]) in
//       debugger, it yields the correct value
void get_coords(Phexdata* pd, gint64* ret) {
    for (int i = 0; i < pd->coord_len; i++) {
        *(ret+i) = elm_slider_value_get(pd->coord_sliders[i]);
    }
}

char* color_f2h(float r, float g, float b) {
    char* ret = (char*)malloc(6*sizeof(char));
    sprintf(ret, "%02x%02x%02x", (int)floor(r*255), (int)floor(g*255), (int)floor(b*255));
    return ret;
}

// TODO: fix race condition here: give char buffer to the func and let it fill it
char* colored_text(char* text, char* color)  {
    char* ret = (char*)malloc(strlen(text)*sizeof(char)+23);
    sprintf(ret, "<span color='#%s'>%s</span>", color, text);
    return ret;
}

